package cn.git.agent;

import cn.git.agent.match.ClassMatch;
import cn.git.agent.match.IndirectMatch;
import cn.git.agent.match.NameMatch;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

import java.util.*;

/**
 * @description: 插件查找器
 * @program: bank-credit-sy
 * @author: lixuchun
 * @create: 2024-12-25
 */
public class PluginFinder {

    /**
     * 用于存储ClassMatch为NameMatcher的插件
     * key: 全类名 ，eg: cn.git.agent.service.impl.UserInfoServiceImpl
     * value: 处理此类的多个插件列表
     */
    private final Map<String, List<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<>();

    /**
     * 用于存储ClassMatch为IndirectMatch间接匹配器的插件
     */
    private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<>();

    /**
     * 构造函数
     *
     * @param pluginDefineList 所有加载的插件
     */
    public PluginFinder(List<AbstractClassEnhancePluginDefine> pluginDefineList) {
        for (AbstractClassEnhancePluginDefine pluginDefine : pluginDefineList) {
            // 获取类匹配器
            ClassMatch classMatch = pluginDefine.enhanceClass();
            if (classMatch == null) {
                continue;
            }
            // 判断是名称匹配
            if (classMatch instanceof NameMatch) {
                NameMatch nameMatch = (NameMatch) classMatch;
                // computeIfAbsent 方法会检查 nameMatchDefine 中是否存在键为 nameMatch.getClassName() 的条目。
                // 如果不存在，则使用提供的 lambda 表达式创建一个新的 LinkedList 并将其放入 Map 中，然后返回这个新的 LinkedList
                // 如果该键已经存在，则直接返回与该键关联的现有列表
                List<AbstractClassEnhancePluginDefine> enhancePluginDefineList = nameMatchDefine.computeIfAbsent(nameMatch.getClassName(), a -> new LinkedList<>());
                // 添加插件到名称匹配列表
                enhancePluginDefineList.add(pluginDefine);
            } else {
                // 间接匹配
                signatureMatchDefine.add(pluginDefine);
            }
        }
    }

    /**
     * 构建匹配器,返回已加载最终拼接的类匹配器条件
     *
     * @return plugin1_junction.or(plugin2_junction.or(plugin3_junction)).....
     */
    public ElementMatcher<? super TypeDescription> buildTypeMatch() {
        /**
         * 注释提到“当某个类第一次被加载时，都会回调该方法”，这意味着每当 JVM 加载一个新的类时，ByteBuddy 会调用这个 matches 方法来判断是否对该类进行增强或其他操作。
         * 例如，如果 nameMatchDefine 中包含 cn.git.TestService，那么当 cn.git.TestService 类被加载时，matches 方法会返回 true，从而触发相应的插件逻辑。
         */
        ElementMatcher.Junction<? super TypeDescription> junction = new ElementMatcher.Junction.AbstractBase<NamedElement>() {
            @Override
            public boolean matches(NamedElement target) {
                // 当某个类第一次被加载时，都会回调该方法 eg: cn.git.TestService
                return nameMatchDefine.containsKey(target.getActualName());
            }
        };

        // 只增强类，不增强接口
        junction = junction.and(ElementMatchers.not(ElementMatchers.isInterface()));

        // 添加间接匹配插件
        for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {
            ClassMatch classMatch = pluginDefine.enhanceClass();
            if (classMatch instanceof IndirectMatch) {
                IndirectMatch indirectMatch = (IndirectMatch) classMatch;
                junction = junction.or(indirectMatch.buildJunction());
            }
        }
        return junction;
    }

    /**
     * 通过插件类类型获取插件列表
     *
     * @param typeDescription
     * @return
     */
    public List<AbstractClassEnhancePluginDefine> find(TypeDescription typeDescription) {
        // 返回参数列表
        List<AbstractClassEnhancePluginDefine> matchedPlugins = new LinkedList<AbstractClassEnhancePluginDefine>();
        // 获取全类名称
        String typeName = typeDescription.getTypeName();

        // 获取名称匹配插件
        if (nameMatchDefine.containsKey(typeName)) {
            matchedPlugins.addAll(nameMatchDefine.get(typeName));
        }

        // 获取间接匹配插件SignatureMatchDefine
        for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {
            IndirectMatch match = (IndirectMatch) pluginDefine.enhanceClass();
            if (match.isMatch(typeDescription)) {
                matchedPlugins.add(pluginDefine);
            }
        }

        return matchedPlugins;
    }
}
